package io.github.bloquesoft.entity.clazz;

import io.github.bloquesoft.entity.clazz.annotation.ParentId;
import io.github.bloquesoft.entity.definition.EntityDefinition;
import io.github.bloquesoft.entity.cache.EntityDefinitionCache;
import lombok.extern.slf4j.Slf4j;
import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl;

import javax.persistence.Id;
import javax.persistence.Transient;
import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

@Slf4j
public class ClassEntityDefinitionFactory
{
    private final EntityDefinitionCache entityDefinitionCache;

    private final Map<String, ClassEntityDefinition> tempCache = new HashMap<>();

    public ClassEntityDefinitionFactory(EntityDefinitionCache entityDefinitionCache) {
        this.entityDefinitionCache = entityDefinitionCache;
    }

    public ClassEntityDefinition get(Class<?> clazz)
    {
        String entityId = ClassEntityUtils.entityId(clazz);
        EntityDefinition entityDefinition = entityDefinitionCache.get(entityId);
        if (entityDefinition != null) {
            if(entityDefinition instanceof ClassEntityDefinition)
            {
                return (ClassEntityDefinition) entityDefinition;
            }
            log.error("Entity definition with id :" + entityId + " is not class entity definition type.");
            return null;
        }
        else {
            synchronized (ClassEntityDefinitionFactory.class)
            {
                EntityDefinition ed = entityDefinitionCache.get(entityId);
                if (ed == null)
                {
                    ClassEntityDefinition classEntityDefinition = factory(clazz);
                    this.entityDefinitionCache.add(classEntityDefinition.getId(), classEntityDefinition);
                    return classEntityDefinition;
                }
                else {
                    return (ClassEntityDefinition) ed;
                }
            }
        }
    }

    private ClassEntityDefinition factory(Class<?> clazz)
    {
        ClassEntityDefinition classEntityDefinition = new ClassEntityDefinition(ClassEntityUtils.entityId(clazz), clazz);
        tempCache.put(classEntityDefinition.getId(), classEntityDefinition);
        for(Field field : clazz.getDeclaredFields()) {

            if (field.isAnnotationPresent(Transient.class)) {
                continue;
            }
            if (ClassPojoPropertyDefinition.isPojoPropertyType(field.getType())) {
                ClassPojoPropertyDefinition classEntityPojoProperty = new ClassPojoPropertyDefinition(field);
                classEntityDefinition.addPojoProperty(classEntityPojoProperty);
                if (field.isAnnotationPresent(Id.class)) {
                    classEntityDefinition.setClassPrimaryKeyProperty(classEntityPojoProperty);
                }
                if (field.isAnnotationPresent(ParentId.class)){
                    classEntityPojoProperty.setIsParentId(true);
                }
                continue;
            }
            if (Collection.class.isAssignableFrom(field.getType()))
            {
                if (field.getGenericType() instanceof ParameterizedTypeImpl)
                {
                    ParameterizedTypeImpl parameterizedType = (ParameterizedTypeImpl)field.getGenericType();
                    for (Type actualType : parameterizedType.getActualTypeArguments()) {
                        ClassEntityDefinition listItemEntityDefinition = this.factory((Class<?>) actualType);
                        ClassListPropertyDefinition classEntityListProperty = new ClassListPropertyDefinition(field, listItemEntityDefinition);
                        classEntityDefinition.addListProperty(classEntityListProperty);
                    }
                }
                continue;
            }
            log.error("Not known property type, class:" + clazz.getName() + ", field:" + field.getName());
        }
        if (classEntityDefinition.getPrimaryKeyProperty() == null)
        {
            throw new IllegalArgumentException("Could not find primary key field setting in class " + clazz.getName());
        }
        entityDefinitionCache.add(classEntityDefinition.getId(), classEntityDefinition);
        tempCache.remove(classEntityDefinition.getId());
        return classEntityDefinition;
    }
}